#include "elblocks.h"
#include "ui_elblocks.h"
#include <QPainter>
#include <QTimer>
#include <QTextStream>
#include "ELShapeO.h"
#include <QKeyEvent>
#include <QtWidgets/QMessageBox>

ELBlocks::ELBlocks(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::ELBlocks),
    isPlaying(false),
    score(0), level(1), lines(0), peek(0),
    isEndKeyPressed(false),
    isGameOver(false)

{
    ui->setupUi(this);
    // set window's size.
    this->setMaximumSize(500, 620);
    this->setMinimumSize(500, 620);

    // add action to play button.
    connect(ui->startBtn, SIGNAL(clicked()), this, SLOT(startBtnClicked()));

    // create timer.
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(gameTick()));

    // init block array. hasBlock[][].
    hasBlock = new bool * [NumBlocksVertical];
    for (int i=0; i<NumBlocksVertical; ++i) {
        hasBlock[i] = new bool[NumBlocksHorizontal];
        memset(hasBlock[i], false, NumBlocksHorizontal);
    }

    // create color array. colors[][][].
    colors = new QColor ** [NumBlocksVertical];
    for (int i=0; i<NumBlocksVertical; ++i) {
        colors[i] = new QColor*[NumBlocksHorizontal];
        for (int j=0; j<NumBlocksHorizontal; ++j) {
            colors[i][j] = new QColor();
        }
    }

    // create KindOfShapes shapes.
    for (int i=0; i<KindOfShapes; ++i) {
        shapes[i] = new ELShapeO(hasBlock, colors, NumBlocksVertical, NumBlocksHorizontal);
    }

    currentShape = rand() % KindOfShapes;
    shapes[currentShape]->setBlockColor(QColor::fromRgb(rand() % 255, rand() % 255, rand() % 255));
    nextShape = rand() % KindOfShapes;
    nextShapeColor = QColor::fromRgb(rand() % 255, rand() % 255, rand() % 255);
    shapes[nextShape]->setState(rand() % 4);
    ui->gameOverLbl->hide();
}

ELBlocks::~ELBlocks()
{
    delete ui;
    delete timer;

    // release hasBlock[][].
    for (int i=0; i<NumBlocksVertical; ++i) {
        delete hasBlock[i];
    }
    delete hasBlock;

    // release colors[][][].
    for (int i=0; i<NumBlocksVertical; ++i) {
        for (int j=0; j<NumBlocksHorizontal; ++j) {
            delete colors[i][j];
        }
        delete colors[i];
    }
    delete colors;

    for (int i=0; i<KindOfShapes; ++i) {
        delete shapes[i];
    }
}

void ELBlocks::paintEvent(QPaintEvent *) {
    drawGameTable();
}

// draw lines and blocks.
void ELBlocks::drawGameTable() {
    QPainter painter(this);

    QPen tablePen;
    tablePen.setWidth(1);
    tablePen.setColor(QColor::fromRgb(255, 255, 0, 255));
    tablePen.setStyle(Qt::DotLine);
    painter.setPen(tablePen);
    // horizontal lines.
    for (int i=0; i<=NumBlocksVertical; ++i) {
        painter.drawLine(TableLeft, TableTop + i * BlockWidth
                         , TableLeft + NumBlocksHorizontal * BlockWidth, TableTop + i * BlockWidth);
    }

    // vertical lines.
    for (int i=0; i<=NumBlocksHorizontal; ++i) {
        painter.drawLine(TableLeft + i * BlockWidth, TableTop
                         , TableLeft + i * BlockWidth, TableTop + NumBlocksVertical * BlockWidth);
    }

    // sepeator.
    QPen pen;
    pen.setStyle(Qt::SolidLine);
    pen.setWidth(3);
    pen.setColor(QColor::fromRgb(85, 255, 127)/*255, 255, 0, 255)*/);
    painter.setPen(pen);
    int progress = peek * BlockWidth;
    int seperatorLeft = TableLeft + NumBlocksHorizontal*BlockWidth + 15;
    // draw empty progress.
    painter.drawLine(seperatorLeft, TableTop, seperatorLeft, this->height() - 10 - progress);
    // draw progress.
    pen.setColor(QColor::fromRgb(255, 0, 128, 255));
    painter.setPen(pen);
    painter.drawLine(seperatorLeft, this->height() - 10 - progress, seperatorLeft, this->height() - 10);


    // fill blocks.
    for (int row=0; row < NumBlocksVertical; ++row) {
        for (int col=0; col < NumBlocksHorizontal; ++col) {
            if (hasBlock[row][col]) {
                fillBlockWithColor(&painter, row, col, colors[row][col]);
            }
        }
    }

    // draw next shape.
    pen.setWidth(1);
    pen.setColor(QColor::fromRgb(122, 231, 21, 109));
    painter.setPen(pen);
    int nextLeft = seperatorLeft + (this->width() - seperatorLeft - 4 * BlockWidth) / 2, nextTop = TableTop;
    /*
    // horizon lines.
    for (int i=0; i<5; ++i) {
        painter.drawLine(nextLeft, nextTop + i * BlockWidth
                         , nextLeft + 4 * BlockWidth, nextTop + i * BlockWidth);
    }
    // vertical lines.
    for (int i=0; i<5; ++i) {
        painter.drawLine(nextLeft + i * BlockWidth, nextTop
                         , nextLeft + i * BlockWidth, nextTop + 4 * BlockWidth);
    }
    */
    // next shape.
    for (int i=0; i<4; ++i) {
        painter.fillRect(nextLeft + (shapes[nextShape]->getLayout()[i] % 4) * BlockWidth + 2,
                         nextTop + (shapes[nextShape]->getLayout()[i] / 4) * BlockWidth + 2,
                         BlockWidth - 2, BlockWidth - 2, nextShapeColor);
    }

    // score, level, lines.
    ui->scoreLbl->setText(tr("%1").arg(score));
    ui->levelLbl->setText(tr("%1").arg(level));
    ui->linesLbl->setText(tr("%1").arg(lines));
}

// play and pause button action.
void ELBlocks::startBtnClicked() {
    if (isPlaying) {
        isPlaying = false;
        ui->startBtn->setText(tr("Play"));
        timer->stop();
    }
    else {
        isPlaying = true;
        ui->startBtn->setText(tr("Pause"));
        if (isGameOver) {
            isGameOver = false;
            ui->gameOverLbl->hide();
            resetGame();
        }
        timer->start(TimerTickNormal / level);
    }
}

// timer action.
void ELBlocks::gameTick() {
    // if game is paused or stopped, doing nothing.
    if (!isPlaying) {
        return;
    }

    // set nextShape as the currentShape, and generate the next shape.
    // init currentShape.
    if (!shapes[currentShape]->moveDown()) {
        // clear full line(s) & check if the game is over. prepare next shape.
        checkFullLinesOrIsOver();
    }
    update();
}

// keyboard action.
void ELBlocks::keyPressEvent(QKeyEvent * event) {
    event->accept();

    if (!isPlaying || isEndKeyPressed) return;

    switch (event->key()) {
        case Qt::Key_Left:
            shapes[currentShape]->moveLeft();
            update();
            break;
        case Qt::Key_Right:
            shapes[currentShape]->moveRight();
            update();
            break;
        case Qt::Key_Up:
            shapes[currentShape]->rotate();
            update();
            break;
        case Qt::Key_Down:
            shapes[currentShape]->moveDown();
            update();
            break;
        case Qt::Key_End:
            isEndKeyPressed = true;
            shapes[currentShape]->moveToBottom();
            update();
            break;
        default:
            break;
    }
}
// fill the block specified by the coordination passed in with fillColor.
void ELBlocks::fillBlockWithColor(QPainter * pPainter, int row, int col, QColor* fillColor) {
    int left = TableLeft + col * BlockWidth + 2;
    int top = TableTop + NumBlocksVertical * BlockWidth - (row + 1) * BlockWidth + 2;
    QRect rect(left, top, BlockWidth - 2, BlockWidth - 2);
    pPainter->fillRect(rect, *fillColor);
}

void ELBlocks::checkFullLinesOrIsOver() {
    // set peek.
    findPeek();
    update();

    // clear full line(s) and add to lines(total).
    int linesCleared = clearFullLines();
    lines += linesCleared;

    // update score.
    switch (linesCleared) {
        case 0:
            break;
        case 1:
            score += 100;
            peek -= 1;
            break;
        case 2:
            score += 200;
            peek -= 2;
            break;
        case 3:
            score += 400;
            peek -= 3;
            break;
        case 4:
            score += 1000;
            peek -= 4;
            break;
        default:
            break;
    }
    // if end key pressed, adding 15 pts.
    if (isEndKeyPressed) {
        isEndKeyPressed = false;
        score += 15;
    }

    // update level.
    setLevel();

    // prepare next shape.
    currentShape = nextShape;
    shapes[currentShape]->setBlockColor(nextShapeColor);
    nextShape = rand() % KindOfShapes;
    nextShapeColor = QColor::fromRgb(rand() % 255, rand() % 255, rand() % 255);
    shapes[nextShape]->setState(rand() % 4);
    if (!shapes[currentShape]->prepare()) {
        isPlaying = false;
        timer->stop();
        isGameOver = true;
        ui->startBtn->setText(tr("Try Again!"));
        ui->gameOverLbl->show();
    }
}

void ELBlocks::findPeek() {
    for (int i=NumBlocksVertical-1; i>=0; --i) {
        // if this line is not empty, then this line's index is peek.
        if (!isLineEmptyAtIndex(i)) {
            peek = i + 1;
            return;
        }
    }
    peek = 0;
}

bool ELBlocks::isLineEmptyAtIndex(int index) {
    for (int i=0; i<NumBlocksHorizontal; ++i) {
        if (hasBlock[index][i]) {
            return false;
        }
    }
    return true;
}

bool ELBlocks::isLineFullAtIndex(int index) {
    for (int i=0; i<NumBlocksHorizontal; ++i) {
        if (!hasBlock[index][i]) {
            return false;
        }
    }
    return true;
}

int ELBlocks::clearFullLines() {
    int linesCleared(0);
    for (int i=shapes[currentShape]->top();
         i>shapes[currentShape]->top() - shapes[currentShape]->height();
         --i) {
        if (i >=0 && isLineFullAtIndex(i)) {
            // delete this row. move the blocks above down until peek.
            for (int j=i; j<NumBlocksVertical-1; ++j) {
                for (int k=0; k<NumBlocksHorizontal; ++k) {
                    hasBlock[j][k] = hasBlock[j+1][k];
                    // rearrange color array.
                    // todo: check this line
                    colors[j][k] = colors[j+1][k];
                }
            }
            ++linesCleared;
        }
    }
    return linesCleared;
}


void ELBlocks::setLevel() {
    int newLevel(0);
    if (score <= 10000) {
        newLevel = score / 500 + 1;
    }
    else {
        newLevel = 21 + (score - 10000) / 1000;
    }
    /*
    int levelThresholds[] = {500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 10000000};
    for (int i=0; i<sizeof(levelThresholds) / sizeof(levelThresholds[0]); ++i) {
        if (score < levelThresholds[i]) {
            newLevel = i + 1;
            break;
        }
    }
    */
    if (newLevel == 0) {
        QTextStream out(stdout);
        out << "level error!!!\n";
        exit(-1);
    }

    if (newLevel != level) {
        level = newLevel;
        timer->stop();
        timer->start(TimerTickNormal / level);
    }
}

void ELBlocks::resetGame() {
    // clear table.
    for (int i=0; i<NumBlocksVertical; ++i) {
        memset(hasBlock[i], false, NumBlocksHorizontal);
    }
    score  = lines = peek = 0;
    isEndKeyPressed = false;
    level = 1;
    currentShape = 0;
    //shapes[currentShape]->reset();
    shapes[currentShape]->prepare();
    update();
    nextShape = rand() % KindOfShapes;
    nextShapeColor = QColor::fromRgb(rand() % 255, rand() % 255, rand() % 255);
    shapes[nextShape]->setState(rand() % 4);
}
